package com.zengjianjun.es.demo.highlevelclient.options;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;

/**
 * es条件-时间选项
 * <hr/>
 * <p/>
 * TimeOption作为 elasticsearch 查询条件中的一项。即：当需要以日期作为条件查询 elasticsearch 时，可以使用它。
 * 例如：
 * <pre class="code">
 *      EsGaidQuery.EsGaidQueryBuilder builder = EsGaidQuery.builder()
 *              .aField(FieldValue.<String>builder().value(aField).conditionOption(ConditionOption.equal).build())
 *              .bField(FieldValue.<Integer>builder().value(bField).conditionOption(ConditionOption.or).build())
 *              .cField(FieldValue.<Integer>builder().value(cField).conditionOption(ConditionOption.not_equal).build())
 *              .dField(FieldValue.<List<String>>builder().value(dField).conditionOption(ConditionOption.or).build())
 *              .timeOption(imeOption.builder()
 *                     .field("createTime")
 *                     .interval(0)
 *                     .startTime(startTime)
 *                     .endTime(endTime)
 *                     .build());
 *          </pre>
 * 其中，<i> createTime.keyword </i> 代表的是 elasticsearch 中存储的时间字段的名称，0代表的是 <i> 时间偏移量 </i>，单位（小时）。<br/>
 * <p/>
 * 关于偏移量：
 * <p style="text-indent:32.4px;">
 * 是为了解决：查询某些时间比如 <i> @timestamp </i> 时，由于 elasticsearch 所在集群物理机的位置等原因，可能与客户端存在时差，诸如此类情况。
 * </p>
 * <p/>
 * 除此之外，你还可以使用别的方式来获得一个 TimeOption ，看看它的其他构造方法你就明白了：
 * <pre class="code">
 *     new TimeOption("createTime.keyword", 0).range(startTime, endTime);
 *     new TimeOption("createTime.keyword", 0, startTime, endTime);
 *     new TimeOption("createTime.keyword", 0).range(startTime, String startTimePattern, Date endTime, String endTimePattern);
 *  </pre>
 * 关于startTimePattern, endTimePattern：<br/>
 * <p style="text-indent:32.4px;"/>这两个参数可以被指定：查询 elasticsearch 时，startTime与endTime确切的值的格式是什么。
 * <p style="text-indent:32.4px;"/>如果未指定，默认为 "yyyy-MM-dd 00:00:00"、"yyyy-MM-dd 23:59:59"。
 * <p/>
 * <b>注意 !!!</b>
 * <p style="text-indent:32.4px;"/>
 * 当你传入的 startTime, endTime 为<i>字符串类型时</i>，请确保它们可以被解析为："yyyy-MM-dd"
 * <hr/>
 *
 * <p>
 *
 * @author zengjianjun
 * </p>
 */
@Slf4j
@Getter
public class TimeOption {

    /**
     * 字段名称
     */
    private final String field;
    /**
     * 开始时间
     */
    private String startTime;
    /**
     * 结束时间
     */
    private String endTime;

    // 字符串类型日期参数，默认设定前转化为此格式
    private static final String DATE_PATTERN = "yyyy-MM-dd";

    // 默认的设定后的开始时间格式
    private static final String DEFAULT_START_PATTERN = "yyyy-MM-dd 00:00:00";
    // 默认的设定后的结束时间格式
    private static final String DEFAULT_END_PATTERN = "yyyy-MM-dd 23:59:59";

    // es中时间的偏移量
    private Integer interval = 0;

    public TimeOption(String field, Integer interval) {
        this.field = field;
        this.interval = interval;
    }

    public TimeOption(String field, Integer interval, String startTime, String endTime) {
        this.field = field;
        this.interval = interval;
        this.startTime = startTime;
        this.endTime = endTime;
    }

    public TimeOption(String field, Integer interval, Date startTime, Date endTime) {
        this.field = field;
        this.interval = interval;
        this.startTime = beginAndFormat(startTime, DEFAULT_START_PATTERN);
        this.endTime = endAndFormat(endTime, DEFAULT_END_PATTERN);
    }

    public static TimeOptionBuilder builder() {
        return new TimeOptionBuilder();
    }

    /**
     * startTime - endTime
     */
    public TimeOption range(Date startTime, Date endTime) {
        if (startTime == null && endTime == null) {
            return null;
        }
        if (startTime == null) {
            return setEndTime(endTime, DEFAULT_START_PATTERN);
        }
        if (endTime == null) {
            return setStartTime(startTime, DEFAULT_END_PATTERN);
        }
        this.startTime = beginAndFormat(startTime, DEFAULT_START_PATTERN);
        this.endTime = endAndFormat(endTime, DEFAULT_END_PATTERN);
        return this;
    }

    public TimeOption range(Date startTime, String startTimePattern, Date endTime, String endTimePattern) {
        if (startTime == null && endTime == null) {
            return null;
        }
        if (startTime == null) {
            return setEndTime(endTime, startTimePattern);
        }
        if (endTime == null) {
            return setStartTime(startTime, endTimePattern);
        }
        this.startTime = beginAndFormat(startTime, startTimePattern);
        this.endTime = endAndFormat(endTime, endTimePattern);
        return this;
    }

    public TimeOption range(String startTime, String endTime) {
        if (startTime == null && endTime == null) {
            return null;
        }
        if (startTime == null) {
            return setEndTime(endTime, DEFAULT_START_PATTERN);
        }
        if (endTime == null) {
            return setStartTime(startTime, DEFAULT_END_PATTERN);
        }
        this.startTime = beginAndFormat(startTime, DEFAULT_START_PATTERN);
        this.endTime = endAndFormat(endTime, DEFAULT_END_PATTERN);
        return this;
    }

    public TimeOption range(String startTime, String startTimePattern, String endTime, String endTimePattern) {
        if (startTime == null && endTime == null) {
            return null;
        }
        if (startTime == null) {
            return setEndTime(endTime, startTimePattern);
        }
        if (endTime == null) {
            return setStartTime(startTime, endTimePattern);
        }
        this.startTime = beginAndFormat(startTime, startTimePattern);
        this.endTime = endAndFormat(endTime, endTimePattern);
        return this;
    }

    /**
     * 24小时内
     */
    public TimeOption in24h() {
        String h = "now-24h";
        this.startTime = interval > 0 ? h + "+" + interval + "h" : h;
        return this;
    }

    /**
     * 当天
     */
    public TimeOption currentDay() {
        String h = "now";
        this.startTime = interval > 0 ? h + "+" + interval + "h/d" : h;
        return this;
    }

    /**
     * 本周内
     */
    public TimeOption currentWeek() {
        String h = "now-24";
        this.startTime = interval > 0 ? h + "+" + interval + "h/w" : h;
        return this;
    }

    /**
     * 本月内
     */
    public TimeOption currentMonth() {
        String h = "now-24";
        this.startTime = interval > 0 ? h + "+" + interval + "h/m" : h;
        return this;
    }

    /**
     * 近 t 天内
     */
    public TimeOption near(int t) {
        String h = "now-24";
        this.startTime = interval > 0 ? h + "+" + interval + "h-" + t + "d/d" : h;
        return this;
    }

    private TimeOption setStartTime(Date startTime, String startTimePattern) {
        if (startTime != null) {
            this.startTime = beginAndFormat(startTime, startTimePattern);
        }
        return this;
    }

    private TimeOption setEndTime(Date endTime, String endTimePattern) {
        if (endTime != null) {
            this.endTime = endAndFormat(endTime, endTimePattern);
        }
        return this;
    }

    private String beginAndFormat(Date start, String startTimePattern) {
        return DateUtil.format(DateUtil.offset(DateUtil.beginOfDay(start), DateField.HOUR_OF_DAY, this.interval), startTimePattern);
    }

    private String endAndFormat(Date end, String endTimePattern) {
        return DateUtil.format(DateUtil.offset(DateUtil.endOfDay(end), DateField.HOUR_OF_DAY, this.interval), endTimePattern);
    }

    private TimeOption setStartTime(String startTime, String startTimePattern) {
        if (startTime != null) {
            this.startTime = beginAndFormat(startTime, startTimePattern);
        }
        return this;
    }

    private TimeOption setEndTime(String endTime, String endTimePattern) {
        if (endTime != null) {
            this.endTime = endAndFormat(endTime, endTimePattern);
        }
        return this;
    }

    private String beginAndFormat(String start, String startTimePattern) {
        return DateUtil.format(DateUtil.offset(DateUtil.beginOfDay(DateUtil.parseByPatterns(start, DATE_PATTERN).getTime()), DateField.HOUR_OF_DAY, this.interval), startTimePattern);
    }

    private String endAndFormat(String end, String endTimePattern) {
        return DateUtil.format(DateUtil.offset(DateUtil.endOfDay(DateUtil.parseByPatterns(end, DATE_PATTERN).getTime()), DateField.HOUR_OF_DAY, this.interval), endTimePattern);
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class TimeOptionBuilder {

        /**
         * 字段名称
         */
        private String field;
        /**
         * 开始时间
         */
        private String startTime;
        /**
         * 结束时间
         */
        private String endTime;

        /**
         * es中时间的偏移量
         */
        private Integer interval = 0;

        public TimeOptionBuilder field(String field) {
            this.field = field;
            return this;
        }

        public TimeOptionBuilder startTime(String startTime) {
            this.startTime = startTime;
            return this;
        }

        public TimeOptionBuilder endTime(String endTime) {
            this.endTime = endTime;
            return this;
        }

        public TimeOptionBuilder interval(int interval) {
            this.interval = interval;
            return this;
        }

        public TimeOption build() {
            return new TimeOption(field, interval, startTime, endTime);
        }
    }
}
